
#ifndef RTDNSMANAGER_H
#define RTDNSMANAGER_H

#if defined(RT_WIN32) && !defined(RT_WIN32_DISABLE_AsyncGetHostByName)
  #define RT_WIN32_ENABLE_AsyncGetHostByName
#endif // RT_WIN32 && !RT_WIN32_DISANABLE_AsyncGetHostByName

#include "RtStdCpp.h"
#include "RtReferenceControl.h"
#include "RtUtilTemplates.h"
#include "RtMutex.h"
#include "RtThreadInterface.h"
#include "RtTimerWrapper.h"
#include <map>
#include <vector>
#include <list>

class CRtThread;
class IRtObserver;
class CRtDnsRecord;

class RT_API_EXPORT CRtDnsManager : public IRtEvent, public CRtTimerWrapperSink
{
public:
	static CRtDnsManager* Instance();

	/**
     * gets ip address from cache or kicks off an asynchronous host lookup.
     *
     * @param aRecord
     *        return DNS record corresponding to the given hostname.
     * @param aHostName
     *        the hostname or IP-address-literal to resolve.
     * @param aObserver
     *        the listener to be notified when the result is available.
     * @param aBypassCache
     *        if true, the internal DNS lookup cache will be bypassed.
     * @param aThreadListener
     *        optional parameter (may be null).  if non-null, this parameter
     *        specifies the CRtThread on which the listener's
     *        OnObserve should be called.  however, if this parameter is
     *        null, then OnObserve will be called on current thread.
     *
     * @return 
     *        if RT_OK, <aRecord> is filled with corresponding record.
     *        else if RT_ERROR_WOULD_BLOCK, <aObserver> will be callback late only  
     *   once. You should call CancelResolve() before callback.
     *        else resolve hostname failed.
     */
	RtResult AsyncResolve(
		CRtDnsRecord *&aRecord,
		const CRtString &aHostName,
		IRtObserver *aObserver = NULL,
		BOOL aBypassCache = FALSE,
		CRtThread *aThreadListener = NULL);

	/// <aObserver> will not notified after calling this function.
	RtResult CancelResolve(IRtObserver *aObserver);

	/// clear the <aHostName> in the cache and resolve it again.
	/// return RT_ERROR_WOULD_BLOCK: is relsoving.
	/// return RT_ERROR_FAILURE: relsove failed.
	RtResult RefreshHost(const CRtString &aHostName);

	/// close and release any resoruce.
	RtResult Shutdown();
#ifdef RT_WIN32_ENABLE_AsyncGetHostByName
	void DestroyDnsWindow();
#endif

	/**
     * gets ip address from cache or resolve it at once,
	 * the current thread will be blocking when resolving.
     *
     * @param aRecord
     *        return DNS record corresponding to the given hostname.
     * @param aHostName
     *        the hostname or IP-address-literal to resolve.
     * @param aBypassCache
     *        if true, the internal DNS lookup cache will be bypassed.
     *
     * @return 
     *        if RT_OK, <aRecord> is filled with corresponding record.
     *        else resolve hostname failed.
     */
	RtResult SyncResolve(
		CRtDnsRecord *&aRecord, 
		const CRtString &aHostName,
		BOOL aBypassCache = FALSE);

	RtResult GetLocalIps(CRtDnsRecord *&aRecord);

protected:
	// interface IRtEvent
	virtual RtResult OnEventFire();
	virtual void OnDestorySelf();

	// interface CRtTimerWrapperSink
	virtual void OnTimer(CRtTimerWrapper* aId);

private:
	CRtDnsManager();
	virtual ~CRtDnsManager();
	friend class CRtSingletonT<CRtDnsManager>;

	int BeginResolve_l(CRtDnsRecord *aRecord);
	int DoGetHostByName_l(CRtDnsRecord *aRecord);
	RtResult TryAddObserver_l(IRtObserver *aObserver, CRtThread *aThreadListener, const CRtString &aHostName);
	RtResult Resolved_l(CRtDnsRecord *aRecord, int aError, BOOL aCallback = TRUE);
	RtResult DoCallback_l(int aError, const CRtString &aHostName);
	RtResult FindInCache_l(CRtDnsRecord *&aRecord, const CRtString &aHostName);
	void CopyHostent_i(CRtDnsRecord *aRecord, struct hostent *aHostent);

#ifdef RT_WIN32_ENABLE_AsyncGetHostByName
	RtResult CreateDnsWindow();
	int DoAsyncHostByName_l(CRtDnsRecord *aRecord);
#endif
	RtResult SpawnDnsThread_l();
//#endif // RT_WIN32_ENABLE_AsyncGetHostByName

private:
	typedef std::map<CRtString, CRtAutoPtr<CRtDnsRecord> > CacheRecordsType;
	CacheRecordsType m_CacheRecords;
	typedef std::list<CRtAutoPtr<CRtDnsRecord> > PendingRecordsType;
	PendingRecordsType m_PendingRecords;

	class CObserverAndListener : public IRtEvent
	{
	public:
		CObserverAndListener(CRtDnsManager *aDnsManager, 
							 IRtObserver *aObserver, 
							 CRtThread *aThreadListener, 
							 int aError, 
							 const CRtString &aHostName)
			: m_pDnsManager(aDnsManager)
			, m_pObserver(aObserver)
			, m_pThreadListener(aThreadListener)
			, m_nError(aError)
			, m_strHostName(aHostName)
		{
			RT_ASSERTE(m_pDnsManager);
			RT_ASSERTE(m_pObserver);
			RT_ASSERTE(m_pThreadListener);
		}

		bool operator == (const CObserverAndListener &aRight)
		{
			return m_pObserver == aRight.m_pObserver;
		}

		bool operator == (IRtObserver *aObserver)
		{
			return m_pObserver == aObserver;
		}

		// interface IRtEvent
		virtual RtResult OnEventFire();

		CRtDnsManager *m_pDnsManager;
		IRtObserver *m_pObserver;
		CRtThread *m_pThreadListener;
		int m_nError;
		CRtString m_strHostName;
	};
	typedef std::vector<CObserverAndListener> ObserversType;
	ObserversType m_Observers;
	
	typedef CRtMutexThread MutexType;
	MutexType m_Mutex;

#ifdef RT_WIN32_ENABLE_AsyncGetHostByName
	static LRESULT CALLBACK DnsEventWndProc(
		HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	HWND m_hwndDsnWindow;
	BOOL m_bIsEL98;			//the flag to id win98 or lower
#endif
	CRtThread *m_pThreadDNS;
//#endif // RT_WIN32_ENABLE_AsyncGetHostByName
	CRtThread *m_pThreadNetwork;
	CRtTimerWrapper m_TimerExpired;
};

#ifdef RT_WIN32
  #define RT_NETDB_BUF_SIZE MAXGETHOSTSTRUCT 
#else
  #define RT_NETDB_BUF_SIZE 1024 
#endif // RT_WIN32

class /*RT_API_EXPORT*/ CRtDnsRecord : public CRtReferenceControlMutilThread 
{
public:
	CRtDnsRecord(const CRtString &aHostName
#ifdef RT_WIN32_ENABLE_AsyncGetHostByName
		, BOOL bIsWin98
#endif
	);
	virtual ~CRtDnsRecord();

	// only support IPV4 now.
	class iterator
	{
	public:
		typedef DWORD value_type;

		iterator() : m_ppszAddr(NULL)
			{ }

		iterator(char *aBuf)
		{
			struct hostent *pHeResult = reinterpret_cast<hostent *>(aBuf);
			if  (pHeResult) {
				RT_ASSERTE(pHeResult->h_length == 4);
				m_ppszAddr = pHeResult->h_addr_list;
			}
			else
				m_ppszAddr = NULL;
		}

		iterator& operator++()
		{
			RT_ASSERTE(m_ppszAddr);
			if (m_ppszAddr) {
				m_ppszAddr++;
				if (*m_ppszAddr == NULL)
					m_ppszAddr = NULL;
			}
			return (*this); 
		}

		iterator operator++(int)
		{
			iterator tmp = *this;
			++*this;
			return tmp; 
		}

		value_type operator*() const
		{
			if (m_ppszAddr && *m_ppszAddr)
				return *(reinterpret_cast<value_type*>(*m_ppszAddr));
			else
				return INADDR_NONE;
		}

		bool operator==(const iterator &aRight) const
		{
			return (m_ppszAddr == aRight.m_ppszAddr); 
		}

		bool operator!=(const iterator &aRight) const
		{
			return (!(*this == aRight)); 
		}

	private:
		char ** m_ppszAddr;
	};
	iterator begin()
	{
		RT_ASSERTE(m_State == RSV_SUCCESS);
		if (m_State == RSV_SUCCESS)
			return iterator(m_szBuffer);
		else
			return iterator(NULL);
	}

	iterator end()
	{
		RT_ASSERTE(m_State == RSV_SUCCESS);
		return iterator(NULL);
	}

	CRtString GetHostName()
	{
		return m_strHostName;
	}

private:
	CRtString m_strHostName;
	enum {
		RSV_IDLE,
		RSV_PROCESSING,
		RSV_SUCCESS,
		RSV_FAILED,
	} m_State;
	CRtTimeValue m_tvResolve;
#ifdef RT_WIN32_ENABLE_AsyncGetHostByName
	HANDLE m_HandleResolve;
	BOOL m_bIsEL98;
#else
#endif // RT_WIN32_ENABLE_AsyncGetHostByName
	// contains struct hostent.
	char m_szBuffer[RT_NETDB_BUF_SIZE];

	friend class CRtDnsManager;
};

#endif // !RTDNSMANAGER_H
